Borland Online And The Cobb Group Present:


June, 1994 - Vol. 1 No. 6

Programming in DOS - Adding a DOS environment variable from inside a program

In last month's issue of Borland C++ Developer's Journal, we showed you how to create a class that maintains the addresses of the Program Segment Prefix (PSP) and the environment block for a program. In that article's example, we showed you how to use that class to display both the environment block your program receives from DOS at startup and the environment block for DOS itself.

In this article, we'll show you how to create a class that correctly maintains the internal data of the DOS environment block. To demonstrate this, we'll create an example program that adds a new environment variable to the DOS environment. Before we look at the code, let's review the responsibilities our new class will have.

Taking care of the DOS environment

To view or change a variable from a program's copy of the environment block, you'll typically use the library functions getenv() and putenv(). However, if you want to change an environment variable in the environment block for DOS, you'll need to write code that will manage the block's internal structure.

As we mentioned last month, DOS stores the environment variables as end-to-end strings followed by a null string (a zero-length string with a null terminator). Since DOS stores the variables this way, you'll need to take some precautions before changing them.

If you're going to add a new environment variable, you can merely replace the null string with the string for the new variable. However, if you're going to remove an existing variable or change a variable's value (which involves removing the existing variable and then adding the new version at the end), you may need to rearrange some of the variables that follow the existing variable.

Finally, you need to make sure that when you add a new environment variable, the size of the new environment block won't be larger than the space DOS has allocated for the existing environment block. Otherwise, your program may overwrite the data for another part of DOS. Now let's see how we can implement these features in a class we'll call EnvBlock.

The EnvBlock class

An object of the EnvBlock class will be responsible for three things: copying the existing environment variables into an array, providing a standard interface for retrieving and changing individual variables, and copying the new environment variables back out to the existing environment block. To make the class easy to use, we'll divide these responsibilities among the class's member functions.

First, we'll provide a constructor function to initialize an EnvBlock object's internal data structures. When we finish using an EnvBlock object and delete it, our destructor function will deallocate the data structures that the constructor creates.

To allow us to access the environment variables, we'll provide two member functions­­getenv() and putenv()­­that duplicate the behavior of the C library functions that have the same names. Finally, we'll create two utility member functions­­addenv() and delenv()­­to make our class easier to change in the future.

To see how we've implemented the EnvBlock class, see Listing A, where we show a sample program that uses this class along with the PSP class from last month. In this program, we use a PSP object and an EnvBlock object to add a new DOS environment variable. Before we look at the program, let's examine each of the EnvBlock class's member functions in detail.

The constructor

The constructor (EnvBlock::EnvBlock()) sets the value of each data member in the class. When we call the constructor for the EnvBlock class, we'll pass it one argument­­start­­ which contains the address of the environment block we want to view or modify. The line

startOfEnv(start) {

initializes the startOfEnv data member with the start argument.

Inside the body of the constructor, we create a temporary variable appropriately named tempAddress. The tempAddress variable is a huge character pointer that we'll use to retrieve values from in and around the environment block. (The tempAddress variable needs to be a huge pointer in order for us to change its value correctly.)

To set the value of the envSpaceSize data member, we first set the tempAddress variable to the address of the environment block's size data. We then set the variable envSpaceSize with the value from this location multiplied by 16. For more information on finding this address, see the accompanying article Calculating the environment block's size.

Finally, we call the addenv() private member function with the tempAddress pointer to create a character array (commonly called a string) for each environment variable. Afterwards, the function stores the address of each variable's new string in the envVars array (an array of strings).

The destructor

The destructor (EnvBlock::~EnvBlock()) copies the strings from the envVars array to the old environment block as end-to-end strings. However, if we make changes to the variables that would cause the block to grow larger than the space available (envSpaceSize), the destructor will copy only as many as it can fit in the existing space. The destructor will ignore any environment variables we don't have room for.

Finally, we'll deallocate each string we allocated in the constructor. We'll do this by using the delete operator on each element in the envVars array.

getenv()

The EnvBlock::getenv() member function mimics the behavior of the getenv() library function. Unfortunately, the library function works only with your program's copy of the environment block. This member function will return a pointer to a variable you've copied from the DOS environment block (if you call the constructor with the DOS environment block address).

In general, you'll call the getenv() member function with the name of an environment variable you want to find. However, if you want to remove an environment variable, you can call this function with the form

getenv("LIBPATH=");

Then, getenv() will call the private function delenv() to delete that variable from the list.

If the getenv() member function finds the environment variable you pass as an argument (without the equal sign), it returns the address of that variable from the envVars array. If it can't find the variable or if you remove the variable (by including the equal sign), it returns 0 (a null pointer).

putenv()

The EnvBlock::putenv() member function mimics the behavior of the putenv() library function. As with the getenv() function, the library version of the putenv() function works only with your program's copy of the environment block. This member function can change a variable you've copied from the DOS environment block.

The putenv() member function begins by calling the getenv() member function with the environment variable you pass as its argument. If you call this function with a new value for the environment variable, it will then call the private function addenv() to add the new variable.

addenv()

If the constructor or the putenv() function needs to add a variable, it calls the private member function EnvBlock::addenv(). This function will dynamically allocate and add a new character array and then copy the new environment variable into the array.

delenv()

If the getenv() function needs to remove a variable, it calls the EnvBlock::delenv() private member function. This function will delete the appropriate variable, and then move each of the subsequent variables one position backward in the array.

Putting the EnvBlock class to work

To try out the EnvBlock class, first launch the Borland C++ Integrated Development Environment (IDE). You can use the version 3.1 DOS IDE or the version 4.0 Windows IDE. For the remainder of the article, we'll assume that you're using the 3.1 DOS IDE.

When the IDE menu bar appears, open a new source file window by choosing New from the File menu. When the new window appears, enter the code from Listing A.

When you finish entering the code for ENV2.CPP, choose Save from the File menu. In the Save File As dialog box, enter ENV2.CPP as the name of this file and click OK.

Choose Compiler from the Options menu to display the compiler options submenu. From the submenu, choose Code Generation... to display the Code Generation dialog box, as shown in Figure A. Select the Large radio button in the Model section. Click OK to save this change.


Figure A - You set the memory model to Large in the Code Generation dialog box.

Now, build the program by choosing Make from the Compile menu. When the compiler finishes, the status dialog box will display

 Success : press any key 

at the bottom. Choose Quit from the File menu to quit the IDE.

At the DOS command line, view your current environment variables by entering SET. Typically, you'll see settings similar to

COMSPEC=C:\COMMAND.COM
PATH=C:\BORLANDC\BIN;C:\;\DOS;\UTIL;
TEMP=C:\DOS
PROMPT=$P$G 

Now, run ENV2.EXE by entering ENV2 at the command line. When the program finishes and the command prompt returns (it should run for only a second or so), notice that the command prompt may have changed to C>.

You can view the changes ENV2.EXE made by entering SET at the command line again. Now you should see something similar to

COMSPEC=C:\COMMAND.COM
PATH=C:\BORLANDC\BIN;C:\;\DOS;\UTIL;
TEMP=C:\DOS
BCDJ=JUNE 

The ENV2.EXE program removed the PROMPT environment variable (which may have changed the appearance of your command prompt). The program also added the new environment variable BCDJ and set its value equal to JUNE.

Conclusion

There are a number of environment variables in DOS that you can change from the DOS command line. By creating the EnvBlock class, we've provided a standard interface that allows you to view or change the DOS environment block data.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.